/*
 * Copyright 2007 - The JDPF Project Team (http://www.jdpf.org) 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.jdpf.core.netserializer;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.jdpf.core.kernel.elements.AbstractTransition;
import org.jdpf.core.kernel.elements.ITransition;
import org.jdpf.core.kernel.elements.InputArc;
import org.jdpf.core.kernel.elements.Net;
import org.jdpf.core.kernel.elements.OutputArc;
import org.jdpf.core.kernel.elements.Place;
import org.jdpf.core.kernel.logging.ILogger;
import org.jdpf.core.kernel.logging.LoggerFactory;
import org.jdpf.core.kernel.plugins.BundleToolBox;
import org.jdpf.core.kernel.plugins.IPluginsFrameworkConstants;
import org.jdpf.core.netserializer.services.INetSerializer;
import org.jdpf.core.plugins.blocks.AbstractTransitionPlugin;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * @author Bruno Farina
 * 
 */
public class NetSerializer implements INetSerializer {

	private static final ILogger logger = LoggerFactory.getDefault()
			.getLogger();

	private Document doc;

	private String xmlNetFile;

	private Element root;

	private DocumentBuilder docBuilder;

	public NetSerializer() {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		docBuilder = null;
		try {
			docBuilder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void serializeNet(Net net) {
		doc = docBuilder.newDocument();
		initializeDocument();
		String netName = net.getName();
		if (netName == null) {
			netName = "Defualt Net name";
		}
		root.appendChild(createCommonElement("Name", netName));
		Element el = doc.createElement("jdpf:Tool");
		Element name = createCommonElement("Name", "JDPF");
		Element version = createCommonElement("Version", "1.0.0");
		Element provider = createCommonElement("Provider", "HCKLab");
		el.appendChild(name);
		el.appendChild(version);
		el.appendChild(provider);
		root.appendChild(el);
		List<String> bundleIds = new ArrayList<String>(5);
		for (ITransition t : net.getAllTransitions()) {
			String pluginId = ((AbstractTransitionPlugin) t).getPluginId();
			// String pluginId=(String)
			// t.getClass().getMethod("getPluginId",null).invoke(t,null);
			if (!bundleIds.contains(pluginId)) {
				bundleIds.add(pluginId);
			}
		}
		serializePlugin(bundleIds);
		for (ITransition t : net.getAllTransitions()) {
			String category = ((AbstractTransitionPlugin) t).getCategory();

			// The inspector transition is serialized in serializePlace() method
			if (!category.equals(IPluginsFrameworkConstants.INSPECTOR))
				this.serializeTransition(t);
		}
		for (Place p : net.getAllPlaces()) {
			this.serializePlace(p);
		}
		for (InputArc arc : net.getAllInputArcs()) {
			this.serializeInputArc(arc);
		}
		for (OutputArc arc : net.getAllOutputArcs()) {
			this.serializeOutputArc(arc);
		}
		finalizeXMLSerialization();
	}

	private void serializePlugin(List<String> bundleIds) {
		for (String s : bundleIds) {
			Dictionary headers = BundleToolBox.getDefault().getHeaders(s);
			Element plugin = doc.createElement("jdpf:Plugin");
			plugin.setAttribute("id", s);
			Element version = createCommonElement("Version", (String) headers
					.get("Bundle-Version"));
			Element name = createCommonElement("Name", (String) headers
					.get("Bundle-Name"));
			plugin.appendChild(version);
			plugin.appendChild(name);
			root.appendChild(plugin);
		}
	}

	private void initializeDocument() {
		root = doc.createElement("jdpf:JDPFnet");
		Attr jdpf = doc.createAttribute("xmlns:jdpf");
		jdpf.setNodeValue("http://www.jdpf.org/JDPFNet");
		root.setAttributeNode(jdpf);
		Attr xsi = doc.createAttribute("xmlns:xsi");
		xsi.setNodeValue("http://www.w3.org/2001/XMLSchema-instance");
		root.setAttributeNode(xsi);
		Attr location = doc.createAttribute("xmlns:schemaLocation");
		location
				.setNodeValue("http://www.jdpf.org/JDPFNet misc/XMLSchema/JDPFNet.xsd");
		root.setAttributeNode(location);
	}

	private void serializePlace(Place p) {
		Element place = doc.createElement("jdpf:Place");
		place.appendChild(createCommonElement("Name", p.getName()));
		place.setAttribute("id", p.getId());

		// Check for inspector
		List<ITransition> inspList = p.getInspectors();
		for (ITransition inspector : inspList) {
			// Adding transition element
			String id = null;
			String pluginId = null;
			String name = null;

			try {
				id = (String) inspector.getClass().getMethod(
						"getAbstractTransitionId", (Class[]) null).invoke(
						inspector, (Object[]) null);
				pluginId = (String) inspector.getClass().getMethod(
						"getPluginId", (Class[]) null).invoke(inspector,
						(Object[]) null);
				name = (String) inspector.getClass().getMethod(
						"getAbstractTransitionName", (Class[]) null).invoke(
						inspector, (Object[]) null);

				Element inspElement = doc.createElement("jdpf:Inspector");
				inspElement.setAttribute("id", id);
				inspElement.setAttribute("pluginId", pluginId);
				inspElement.appendChild(createCommonElement("Name", name));

				place.appendChild(inspElement);
			} catch (Exception e) {
				logger.error(this, e.getMessage());
			}
		}

		root.appendChild(place);
	}

	private void serializeTransition(ITransition t) {
		Element tr = doc.createElement("jdpf:Transition");
		AbstractTransition abTr = null;
		String pluginId = null;
		try {
			abTr = (AbstractTransition) t.getClass().getMethod(
					"getAbstractTransition", null).invoke(t, null);
			pluginId = (String) t.getClass().getMethod("getPluginId", null)
					.invoke(t, null);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		String id = abTr.getId();
		String name = abTr.getName();
		tr.setAttribute("id", id);
		tr.setAttribute("pluginId", pluginId);
		tr.appendChild(createCommonElement("Name", name));
		root.appendChild(tr);
	}

	private void serializeInputArc(InputArc arc) {
		Element el = doc.createElement("jdpf:Arc");
		el.appendChild(createCommonElement("Name", arc.getName()));
		el.setAttribute("id", arc.getId());
		el.setAttribute("source", arc.getPlace().getId());
		ITransition tr = arc.getTransition();
		String trId = null;
		;
		try {
			trId = (String) tr.getClass().getMethod("getAbstractTransitionId",
					null).invoke(tr, null);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		el.setAttribute("destination", trId);
		el.setAttribute("inputOrder", arc.getOrder() + "");
		root.appendChild(el);
	}

	private void serializeOutputArc(OutputArc arc) {
		Element el = doc.createElement("jdpf:Arc");
		el.appendChild(createCommonElement("Name", arc.getName()));
		el.setAttribute("id", arc.getId());
		ITransition tr = arc.getTransition();
		String trId = null;
		;
		try {
			trId = (String) tr.getClass().getMethod("getAbstractTransitionId",
					null).invoke(tr, null);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		el.setAttribute("source", trId);
		el.setAttribute("destination", arc.getPlace().getId());
		root.appendChild(el);
	}

	private Element createCommonElement(String name, String text) {
		Element el = doc.createElement("jdpf:" + name);
		el.setTextContent(text);
		return el;
	}

	private void finalizeXMLSerialization() {
		doc.appendChild(root);
		try {
			Transformer transformer = TransformerFactory.newInstance()
					.newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.transform(new DOMSource(doc), new StreamResult(
					new FileOutputStream(xmlNetFile)));
		} catch (TransformerConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TransformerFactoryConfigurationError e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TransformerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		logger.log("[INFO] Net serialize to XML format in file " + xmlNetFile);
	}

	public String getXMLNetFile() {
		return xmlNetFile;
	}

	public void setXMLNetFile(String file) {
		if (file.endsWith(".xml"))
			this.xmlNetFile = file;
		else {
			this.xmlNetFile = file + ".xml";
		}

	}

}
